/*
 * Copyright (c) Rafael Steil
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 1) Redistributions of source code must retain the above 
 * copyright notice, this list of conditions and the 
 * following  disclaimer.
 * 2)  Redistributions in binary form must reproduce the 
 * above copyright notice, this list of conditions and 
 * the following disclaimer in the documentation and/or 
 * other materials provided with the distribution.
 * 3) Neither the name of "Rafael Steil" nor 
 * the names of its contributors may be used to endorse 
 * or promote products derived from this software without 
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT 
 * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 * 
 * This file creation date: Apr 23, 2003 / 10:46:05 PM
 * The JForum Project
 * http://www.jforum.net
 */
package net.jforum.repository;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import net.jforum.SessionFacade;
import net.jforum.cache.CacheEngine;
import net.jforum.cache.Cacheable;
import net.jforum.dao.CategoryDAO;
import net.jforum.dao.ConfigDAO;
import net.jforum.dao.DataAccessDriver;
import net.jforum.dao.ForumDAO;
import net.jforum.dao.UserDAO;
import net.jforum.entities.Category;
import net.jforum.entities.Config;
import net.jforum.entities.Forum;
import net.jforum.entities.LastPostInfo;
import net.jforum.entities.MostUsersEverOnline;
import net.jforum.entities.Post;
import net.jforum.entities.Topic;
import net.jforum.entities.User;
import net.jforum.exceptions.CategoryNotFoundException;
import net.jforum.exceptions.DatabaseException;
import net.jforum.security.PermissionControl;
import net.jforum.security.SecurityConstants;
import net.jforum.util.CategoryOrderComparator;
import net.jforum.util.preferences.ConfigKeys;
import net.jforum.util.preferences.SystemGlobals;

/**
 * Repository for the forums of the System. This repository acts like a cache
 * system, to avoid repetitive and unnecessary SQL queries every time we need
 * some info about the forums. To start the repository, call the method
 * <code>start(ForumModel, CategoryModel)</code>
 * 
 * @author Rafael Steil
 * @version $Id$
 */
public class ForumRepository implements Cacheable {
	private static CacheEngine cache;
	private static ForumRepository instance;
	private static final String FQN = "forumRepository";
	private static final String CATEGORIES_SET = "categoriesSet";
	private static final String RELATION = "relationForums";
	private static final String FQN_MODERATORS = FQN + "/moderators";
	private static final String TOTAL_MESSAGES = "totalMessages";
	private static final String MOST_USERS_ONLINE = "mostUsersEverOnline";
	private static final String LOADED = "loaded";
	private static final String LAST_USER = "lastUser";
	private static final String TOTAL_USERS = "totalUsers";

	/**
	 * @see net.jforum.cache.Cacheable#setCacheEngine(net.jforum.cache.CacheEngine)
	 */
	public void setCacheEngine(CacheEngine engine) {
		cache = engine;
	}

	/**
	 * Starts the repository.
	 * 
	 * @param fm
	 *            The <code>ForumModel</code> instance which will be used to
	 *            retrieve information about the forums.
	 * @param cm
	 *            The <code>CategoryModel</code> instance which will be used
	 *            to retrieve information about the categories.
	 * @throws Exception
	 */
	public synchronized static void start(ForumDAO fm, CategoryDAO cm,
			ConfigDAO configModel) throws Exception {
		instance = new ForumRepository();

		if (cache.get(FQN, LOADED) == null) {
			instance.loadCategories(cm);
			instance.loadForums(fm);
			instance.loadMostUsersEverOnline(configModel);
			instance.loadUsersInfo();

			Integer i = (Integer) cache.get(FQN, TOTAL_MESSAGES);

			if (i == null) {
				cache.add(FQN, TOTAL_MESSAGES, new Integer(0));
			}

			cache.add(FQN, LOADED, "1");
		}
	}

	/**
	 * Gets a category by its id. A call to
	 * 
	 * @link #getCategory(int, int) is made, using the return of
	 *       <code>SessionFacade.getUserSession().getUserId()</code> as
	 *       argument for the "userId" parameter.
	 * 
	 * @param categoryId
	 *            The id of the category to check
	 * @return <code>null</code> if the category is either not found or access
	 *         is denied.
	 * @see #getCategory(int, int)
	 */
	public static Category getCategory(int categoryId) {
		return getCategory(SessionFacade.getUserSession().getUserId(),
				categoryId);
	}

	/**
	 * Gets a category by its id.
	 * 
	 * @param userId
	 *            The user id who is requesting the category
	 * @param categoryId
	 *            The id of the category to get
	 * @return <code>null</code> if the category is either not found or access
	 *         is denied.
	 * @see #getCategory(int)
	 */
	public static Category getCategory(int userId, int categoryId) {
		if (!isCategoryAccessible(userId, categoryId)) {
			return null;
		}

		return (Category) cache.get(FQN, Integer.toString(categoryId));
	}

	public static Category getCategory(PermissionControl pc, int categoryId) {
		if (!isCategoryAccessible(pc, categoryId)) {
			return null;
		}

		return (Category) cache.get(FQN, Integer.toString(categoryId));
	}

	public static Category retrieveCategory(int categoryId) {
		return (Category) cache.get(FQN, Integer.toString(categoryId));
	}

	/**
	 * Check is some category is accessible.
	 * 
	 * @param userId
	 *            The user's id who is trying to get the category
	 * @param categoryId
	 *            The category's id to check for access rights
	 * @return <code>true</code> if access to the category is allowed.
	 */
	public static boolean isCategoryAccessible(int userId, int categoryId) {
		return isCategoryAccessible(SecurityRepository.get(userId), categoryId);
	}

	/**
	 * Check if some category is accessible.
	 * 
	 * @param categoryId
	 *            The category id to check for access rights
	 * @return <code>true</code> if access to the category is allowed.
	 */
	public static boolean isCategoryAccessible(int categoryId) {
		return isCategoryAccessible(SessionFacade.getUserSession().getUserId(),
				categoryId);
	}

	/**
	 * Check is some category is accessible.
	 * 
	 * @param pc
	 *            The <code>PermissionControl</code> instance containing all
	 *            security info related to the user.
	 * @param categoryId
	 *            the category's id to check for access rights
	 * @return <code>true</code> if access to the category is allowed.
	 */
	public static boolean isCategoryAccessible(PermissionControl pc,
			int categoryId) {
		return pc.canAccess(SecurityConstants.PERM_CATEGORY, Integer
				.toString(categoryId));
	}

	/**
	 * Gets all categories from the cache.
	 * 
	 * @return <code>List</code> with the categories. Each entry is a
	 *         <code>Category</code> object.
	 */
	public static List getAllCategories(int userId) {
		PermissionControl pc = SecurityRepository.get(userId);
		List l = new ArrayList();

		Set categoriesSet = (Set) cache.get(FQN, CATEGORIES_SET);

		if (categoriesSet == null) {
			try {
				ForumRepository.instance.loadCategories(DataAccessDriver
						.getInstance().newCategoryDAO());
				ForumRepository.instance.loadForums(DataAccessDriver
						.getInstance().newForumDAO());
			} catch (Exception e) {
				throw new CategoryNotFoundException(
						"Failed to get the category", e);
			}

			categoriesSet = (Set) cache.get(FQN, CATEGORIES_SET);

			if (categoriesSet == null) {
				throw new CategoryNotFoundException(
						"Could not find all categories. There must be a problem "
								+ "with the cache");
			}
		}

		for (Iterator iter = categoriesSet.iterator(); iter.hasNext();) {
			Category c = (Category) iter.next();

			if (isCategoryAccessible(pc, c.getId())) {
				l.add(c);
			}
		}

		return l;
	}

	/**
	 * Get all categories. A call to
	 * 
	 * @link #getAllCategories(int) is made, passing the return of
	 *       <code>SessionFacade.getUserSession().getUserId()</code> as the
	 *       value for the "userId" argument.
	 * 
	 * @return <code>List</code> with the categories. Each entry is a
	 *         <code>Category</code> object.
	 * @see #getAllCategories(int)
	 */
	public static List getAllCategories() {
		return getAllCategories(SessionFacade.getUserSession().getUserId());
	}

	private static Category findCategoryByOrder(int order) {
		for (Iterator iter = ((Set) cache.get(FQN, CATEGORIES_SET)).iterator(); iter
				.hasNext();) {
			Category c = (Category) iter.next();
			if (c.getOrder() == order) {
				return c;
			}
		}

		return null;
	}

	/**
	 * Updates some category. This method only updated the "name" and "order"
	 * fields.
	 * 
	 * @param c
	 *            The category to update. The method will search for a category
	 *            with the same id and update its data.
	 */
	public synchronized static void reloadCategory(Category c) {
		Category current = (Category) cache.get(FQN, Integer
				.toString(c.getId()));
		Category currentAtOrder = findCategoryByOrder(c.getOrder());

		Set tmpSet = new TreeSet(new CategoryOrderComparator());
		tmpSet.addAll((Set) cache.get(FQN, CATEGORIES_SET));

		if (currentAtOrder != null) {
			tmpSet.remove(currentAtOrder);
			cache.remove(FQN, Integer.toString(currentAtOrder.getId()));
		}

		tmpSet.add(c);
		cache.add(FQN, Integer.toString(c.getId()), c);

		if (currentAtOrder != null && c.getId() != currentAtOrder.getId()) {
			tmpSet.remove(current);
			currentAtOrder.setOrder(current.getOrder());
			tmpSet.add(currentAtOrder);

			cache.add(FQN, Integer.toString(currentAtOrder.getId()),
					currentAtOrder);
		}

		cache.add(FQN, CATEGORIES_SET, tmpSet);
	}

	/**
	 * Refreshes a category entry in the cache.
	 * 
	 * @param c
	 *            The category to refresh
	 */
	public synchronized static void refreshCategory(Category c) {
		cache.add(FQN, Integer.toString(c.getId()), c);
		Set s = (Set) cache.get(FQN, CATEGORIES_SET);
		s.remove(c);
		s.add(c);
		cache.add(FQN, CATEGORIES_SET, s);
	}

	public synchronized static void refreshForum(Forum forum) {
		Category c = retrieveCategory(forum.getCategoryId());
		c.addForum(forum);
		refreshCategory(c);
	}

	/**
	 * Remove a category from the cache
	 * 
	 * @param c
	 *            The category to remove. The instance should have the category
	 *            id at least
	 */
	public synchronized static void removeCategory(Category c) {
		cache.remove(FQN, Integer.toString(c.getId()));

		Set s = (Set) cache.get(FQN, CATEGORIES_SET);
		s.remove(c);
		cache.add(FQN, CATEGORIES_SET, s);

		Map m = (Map) cache.get(FQN, RELATION);
		for (Iterator iter = m.values().iterator(); iter.hasNext();) {
			if (Integer.parseInt((String) iter.next()) == c.getId()) {
				iter.remove();
			}
		}

		cache.add(FQN, RELATION, m);
	}

	/**
	 * Adds a new category to the cache.
	 * 
	 * @param c
	 *            The category instance to insert in the cache.
	 */
	public synchronized static void addCategory(Category c) {
		String categoryId = Integer.toString(c.getId());
		cache.add(FQN, categoryId, c);

		Set s = (Set) cache.get(FQN, CATEGORIES_SET);

		if (s == null) {
			s = new TreeSet(new CategoryOrderComparator());
		}

		s.add(c);
		cache.add(FQN, CATEGORIES_SET, s);

		Map relation = (Map) cache.get(FQN, RELATION);
		if (relation == null) {
			relation = new HashMap();
		}

		for (Iterator iter = c.getForums().iterator(); iter.hasNext();) {
			Forum f = (Forum) iter.next();
			relation.put(Integer.toString(f.getId()), categoryId);
		}

		cache.add(FQN, RELATION, relation);
	}

	/**
	 * Gets a specific forum from the cache.
	 * 
	 * @param forumId
	 *            The forum's ID to get
	 * @return <code>net.jforum.Forum</code> object instance or
	 *         <code>null</code> if the forum was not found or is not
	 *         accessible to the user.
	 */
	public static Forum getForum(int forumId) {
		String categoryId = (String) ((Map) cache.get(FQN, RELATION))
				.get(Integer.toString(forumId));

		if (categoryId != null) {
			Category category = (Category) cache.get(FQN, categoryId);

			if (isCategoryAccessible(category.getId())) {
				return category.getForum(forumId);
			}
		}

		return null;
	}

	public static boolean isForumAccessible(int forumId) {
		return isForumAccessible(SessionFacade.getUserSession().getUserId(),
				forumId);
	}

	public static boolean isForumAccessible(int userId, int forumId) {
		int categoryId = Integer.parseInt((String) ((Map) cache.get(FQN,
				RELATION)).get(Integer.toString(forumId)));
		return isForumAccessible(userId, categoryId, forumId);
	}

	public static boolean isForumAccessible(int userId, int categoryId,
			int forumId) {
		return ((Category) cache.get(FQN, Integer.toString(categoryId)))
				.getForum(userId, forumId) != null;
	}

	/**
	 * Adds a new forum to the cache repository.
	 * 
	 * @param forum
	 *            The forum to add
	 */
	public synchronized static void addForum(Forum forum) {
		String categoryId = Integer.toString(forum.getCategoryId());

		Category c = (Category) cache.get(FQN, categoryId);
		c.addForum(forum);
		cache.add(FQN, categoryId, c);

		Map m = (Map) cache.get(FQN, RELATION);
		m.put(Integer.toString(forum.getId()), categoryId);
		cache.add(FQN, RELATION, m);

		Set s = (Set) cache.get(FQN, CATEGORIES_SET);
		cache.add(FQN, CATEGORIES_SET, s);
	}

	/**
	 * Removes a forum from the cache.
	 * 
	 * @param forum
	 *            The forum instance to remove.
	 */
	public synchronized static void removeForum(Forum forum) {
		String id = Integer.toString(forum.getId());
		Map m = (Map) cache.get(FQN, RELATION);
		m.remove(id);
		cache.add(FQN, RELATION, m);

		id = Integer.toString(forum.getCategoryId());

		Category c = (Category) cache.get(FQN, id);
		c.removeForum(forum.getId());
		cache.add(FQN, id, c);

		Set s = (Set) cache.get(FQN, CATEGORIES_SET);
		cache.add(FQN, CATEGORIES_SET, s);
	}

	/**
	 * Reloads a forum. The forum should already be in the cache and <b>SHOULD
	 * NOT</b> have its order changed. If the forum's order was changed, then
	 * you <b>MUST CALL</b>
	 * 
	 * @link Category#changeForumOrder(Forum) <b>BEFORE</b> calling this
	 *       method.
	 * 
	 * @param forum
	 *            The forum to reload its information
	 * @throws Exception
	 */
	public static synchronized void reloadForum(int forumId) throws Exception {
		Forum f = DataAccessDriver.getInstance().newForumDAO().selectById(
				forumId);

		if (((Map) cache.get(FQN, RELATION)).containsKey(Integer
				.toString(forumId))) {
			String id = Integer.toString(f.getCategoryId());
			Category c = (Category) cache.get(FQN, id);

			f.setLastPostInfo(null);
			f.setLastPostInfo(ForumRepository.getLastPostInfo(f));
			c.reloadForum(f);

			cache.add(FQN, id, c);
			Set s = (Set) cache.get(FQN, CATEGORIES_SET);
			cache.add(FQN, CATEGORIES_SET, s);
		}

		getTotalMessages(true);
	}

	public static synchronized void updateForumStats(Topic t, User u, Post p) {
		String f = Integer.toString(t.getForumId());

		if (((Map) cache.get(FQN, RELATION)).containsKey(f)) {
			Forum forum = getForum(t.getForumId());

			SimpleDateFormat df = new SimpleDateFormat(SystemGlobals
					.getValue(ConfigKeys.DATE_TIME_FORMAT));

			LastPostInfo lpi = forum.getLastPostInfo();

			if (lpi == null) {
				lpi = new LastPostInfo();
			}

			lpi.setPostId(p.getId());
			lpi.setPostDate(df.format(p.getTime()));
			lpi.setPostTimeMillis(p.getTime().getTime());
			lpi.setTopicId(t.getId());
			lpi.setTopicReplies(t.getTotalReplies());
			lpi.setUserId(u.getId());
			lpi.setUsername(u.getUsername());

			forum.setLastPostInfo(lpi);

			if (t.getTotalReplies() == 0) {
				forum.setTotalTopics(forum.getTotalTopics() + 1);
			} else {
				forum.setTotalPosts(forum.getTotalPosts() + 1);
			}

			Category c = retrieveCategory(forum.getCategoryId());
			c.reloadForum(forum);

			refreshCategory(c);
		}
	}

	/**
	 * Gets information about the last message posted in some forum.
	 * 
	 * @param forum
	 *            The forum to retrieve information
	 * @return
	 */
	public static LastPostInfo getLastPostInfo(Forum forum) throws Exception {
		LastPostInfo lpi = forum.getLastPostInfo();

		if (lpi == null || !forum.getLastPostInfo().hasInfo()) {
			lpi = DataAccessDriver.getInstance().newForumDAO().getLastPostInfo(
					forum.getId());
			forum.setLastPostInfo(lpi);
		}

		return lpi;
	}

	/**
	 * Gets information about the last message posted in some forum.
	 * 
	 * @param forumId
	 *            The forum's id to retrieve information
	 * @return
	 * @throws Exception
	 */
	public static LastPostInfo getLastPostInfo(int forumId) throws Exception {
		return getLastPostInfo(getForum(forumId));
	}

	/**
	 * Gets information about the moderators of some forum.
	 * 
	 * @param forumId
	 *            The forum to retrieve information
	 * @return
	 */
	public static List getModeratorList(int forumId) {
		List l = (List) cache.get(FQN_MODERATORS, Integer.toString(forumId));

		if (l == null) {
			synchronized (FQN_MODERATORS) {
				try {
					l = DataAccessDriver.getInstance().newForumDAO()
							.getModeratorList(forumId);
					cache.add(FQN_MODERATORS, Integer.toString(forumId), l);
				} catch (Exception e) {
					throw new DatabaseException(e);
				}
			}
		}

		return l;
	}

	public static void clearModeratorList() {
		cache.remove(FQN_MODERATORS);
	}

	public static User lastRegisteredUser() {
		return (User) cache.get(FQN, LAST_USER);
	}

	public static void setLastRegisteredUser(User user) {
		cache.add(FQN, LAST_USER, user);
	}

	public static Integer totalUsers() {
		return (Integer) cache.get(FQN, TOTAL_USERS);
	}

	public static void incrementTotalUsers() {
		Integer i = (Integer) cache.get(FQN, TOTAL_USERS);

		if (i == null) {
			i = new Integer(0);
		}

		cache.add(FQN, TOTAL_USERS, new Integer(i.intValue() + 1));
	}

	/**
	 * Gets the number of messages in the entire board.
	 * 
	 * @return
	 * @throws Exception
	 * @see #getTotalMessages(boolean)
	 */
	public static int getTotalMessages() throws Exception {
		return getTotalMessages(false);
	}

	/**
	 * Gets the number of messags in the entire board.
	 * 
	 * @param fromDb
	 *            If <code>true</code>, a query to the database will be made,
	 *            to retrieve the desired information. If <code>false</code>,
	 *            the data will be fetched from the cache.
	 * @return The number of messages posted in the board.
	 * @throws Exception
	 * @see #getTotalMessages()
	 */
	public static int getTotalMessages(boolean fromDb) throws Exception {
		Integer i = (Integer) cache.get(FQN, TOTAL_MESSAGES);

		int total = i != null ? i.intValue() : 0;

		if (fromDb || total == 0) {
			total = DataAccessDriver.getInstance().newForumDAO()
					.getTotalMessages();
			cache.add(FQN, TOTAL_MESSAGES, new Integer(total));
		}

		return total;
	}

	public static synchronized void incrementTotalMessages() {
		int total = ((Integer) cache.get(FQN, TOTAL_MESSAGES)).intValue();
		cache.add(FQN, TOTAL_MESSAGES, new Integer(total + 1));
	}

	/**
	 * Gets the number of most online users ever
	 * 
	 * @return
	 */
	public static MostUsersEverOnline getMostUsersEverOnline() {
		return (MostUsersEverOnline) cache.get(FQN, MOST_USERS_ONLINE);
	}

	/**
	 * Update the value of most online users ever.
	 * 
	 * @param newValue
	 *            The new value to store. Generally it will be a bigger one.
	 * @throws Exception
	 */
	public static void updateMostUsersEverOnline(MostUsersEverOnline m)
			throws Exception {
		ConfigDAO cm = DataAccessDriver.getInstance().newConfigDAO();
		Config config = cm.selectByName(ConfigKeys.MOST_USERS_EVER_ONLINE);
		if (config == null) {
			// Total
			config = new Config();
			config.setName(ConfigKeys.MOST_USERS_EVER_ONLINE);
			config.setValue(Integer.toString(m.getTotal()));

			cm.insert(config);

			// Date
			config.setName(ConfigKeys.MOST_USER_EVER_ONLINE_DATE);
			config.setValue(Long.toString(m.getTimeInMillis()));

			cm.insert(config);
		} else {
			// Total
			config.setValue(Integer.toString(m.getTotal()));
			cm.update(config);

			// Date
			config.setName(ConfigKeys.MOST_USER_EVER_ONLINE_DATE);
			config.setValue(Long.toString(m.getTimeInMillis()));
			cm.update(config);
		}

		cache.add(FQN, MOST_USERS_ONLINE, m);
	}

	/**
	 * Loads all forums.
	 * 
	 * @throws Exception
	 */
	private void loadForums(ForumDAO fm) throws Exception {
		List l = fm.selectAll();

		Map m = (Map) cache.get(FQN, RELATION);
		if (m == null) {
			m = new HashMap();
		}

		int lastId = 0;
		Category c = null;
		String catId = null;

		for (Iterator iter = l.iterator(); iter.hasNext();) {
			Forum f = (Forum) iter.next();

			if (f.getCategoryId() != lastId) {
				if (c != null) {
					cache.add(FQN, catId, c);
				}

				lastId = f.getCategoryId();
				catId = Integer.toString(f.getCategoryId());
				c = (Category) cache.get(FQN, catId);
			}

			if (c == null) {
				throw new CategoryNotFoundException("Category for forum #"
						+ f.getId() + " not found");
			}

			String forumId = Integer.toString(f.getId());
			c.addForum(f);
			m.put(forumId, catId);
		}

		if (c != null) {
			cache.add(FQN, catId, c);
		}

		cache.add(FQN, RELATION, m);
	}

	private void loadUsersInfo() throws Exception {
		UserDAO udao = DataAccessDriver.getInstance().newUserDAO();
		cache.add(FQN, LAST_USER, udao.getLastUserInfo());
		cache.add(FQN, TOTAL_USERS, new Integer(udao.getTotalUsers()));
	}

	/**
	 * Loads all categories.
	 * 
	 * @throws Exception
	 */
	private void loadCategories(CategoryDAO cm) throws Exception {
		List categories = cm.selectAll();
		Set categoriesSet = new TreeSet(new CategoryOrderComparator());

		for (Iterator iter = categories.iterator(); iter.hasNext();) {
			Category c = (Category) iter.next();

			cache.add(FQN, Integer.toString(c.getId()), c);
			categoriesSet.add(c);
		}

		cache.add(FQN, CATEGORIES_SET, categoriesSet);
	}

	private void loadMostUsersEverOnline(ConfigDAO cm) throws Exception {
		Config config = cm.selectByName(ConfigKeys.MOST_USERS_EVER_ONLINE);
		MostUsersEverOnline mostUsersEverOnline = new MostUsersEverOnline();

		if (config != null) {
			mostUsersEverOnline.setTotal(Integer.parseInt(config.getValue()));

			// We're assuming that, if we have one key, the another one
			// will always exist
			config = cm.selectByName(ConfigKeys.MOST_USER_EVER_ONLINE_DATE);
			mostUsersEverOnline.setTimeInMillis(Long.parseLong(config
					.getValue()));
		}

		cache.add(FQN, MOST_USERS_ONLINE, mostUsersEverOnline);
	}

	public static String getListAllowedForums() throws Exception {
		int n = 0;
		StringBuffer buf = new StringBuffer();

		List allCategories = ForumRepository.getAllCategories();

		for (Iterator iter = allCategories.iterator(); iter.hasNext();) {
			Collection forums = ((Category) iter.next()).getForums();

			for (Iterator tmpIterator = forums.iterator(); tmpIterator
					.hasNext();) {
				Forum f = (Forum) tmpIterator.next();

				if (ForumRepository.isForumAccessible(f.getId())) {
					if (n++ > 0) {
						buf.append(',');
					}

					buf.append(f.getId());
				}
			}
		}

		if (n <= 0) {
			return "-1";
		}

		return buf.toString();
	}
}
